﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Reflection;

namespace SKDB_Matcher.SQL
{
    class CSQLDataType
    {
        /** 
         * DataTypes
         * bit
         *      1/0 bzw. Ja/Nein 
         * datetime
         *      Kombinierte Datums- und Zeitangaben.
         * decimal[(precision [, scale])]
         *      Dezimalzahlen mit fester Zahl der Dezimalstellen. 'precision' legt die Gesamtzahl der Stellen (Vor- und Nachkommastellen) fest und kann zwischen 1 und 38 liegen. 'scale' legt die Zahl der Nachkommastellen fest, muß zwischen 0 und der precision liegen und kann nur gemeinsam mit precision angegeben werden. Standardwert: decimal(18, 4), also bis zu 14 Vorkomma- und 4 Nachkommastellen. 
         * float
         *       Datentyp für ungefähre numerische Daten, etwa Ergebnissen aus Winkelberechnungen. Die Daten werden intern in der Form Mantisse + Exponent (1.5374E+002 = 1.5374 * 10^2 = 153.74) gespeichert
         * int
         *       Ganzzahl (4 Byte) zwischen -2^31 (-2.147.483.648) und 2^31 - 1 (2.147.483.647)
         * money
         *       Datentyp für Währungsangaben im Bereich -2^63 bis 2^63 - 1 ((-922.337.203.685.477,5808 bis 922.337.203.685.477,5807)
         * nvarchar(Länge)
         *       Unicode-Zeichendaten mit einer Länge zwischen 1 und 4000 Zeichen
         * ntext
         *       Unicode-Zeichendaten übergroßer Länge bis zu 1.073.741.823 Zeichen
         * varchar(Länge)
         *       Zeichendaten für Ascii-Zeichen mit einer Länge zwischen 1 und 8000 Zeichen
         * text
         *       Zeichendaten für Ascii mit einer Länge bis zu 2.147.483.647 Zeichen
         * uniqueidentifier
         *       16-Byte großer Datentyp für Rückgabewerte der NewId-Funktion. Es können Spalten mit diesem Datentyp erzeugt werden, ein Wert wird bei der Erstellung eingefügt, die Spalte ist schreibgeschützt. Innerhalb von servern-daten hat eine solche Spalte ansonsten keine Bedeutung. Es kann damit jedoch jeder Zeile auf einfache Weise ein weltweit eindeutiger Wert unabhängig vom Primärschlüssel zugewiesen werden, der vielleicht in anderen Zusammenhängen verwendbar ist.
         * bitSet
         *       Dieser, intern als Integer implementierte Datentyp kann verwendet werden, um anschließend eine Relation von dieser Spalte als Detailspalte auf eine bitSet-Tabelle zu deklarieren. Die bitSet-Tabelle kann 1 - 30 Zeilen enthalten, deren IDs die Folge 1, 2, 4, 8 usw. bilden. Die Zeilen werden als Checkboxen zum Anhaken ausgegeben, es können keine, eine oder mehrere Checkboxen angehakt werden. Die Menge aller angehakten Werte wird als Bitkombination in der Spalte gespeichert.
         * file
         *       Dieser Datentyp wird zunächst als nvarchar(100) definiert. Falls eine FTP-Tabelle deklariert und diesem Datentyp eine Zeile aus der FTP-Tabelle zugeordnet ist, wird innerhalb der internen Masken zusätzlich ein Datei-Upload-Feld bereitgestellt. In Ausgabeseiten wird in diesem Fall nur ein Datei-Upload erzeugt. Die hochgeladene Datei wird bei erfolgreichem Speichern auf den durch die FTP-Daten definierten Server verschoben. Bilder können zusätzlich skaliert, zugeschnitten und durch ein Vorschaubild (= Thumbnail) ergänzt werden.
         * bigInt / smallInt / tinyInt
         *       Ganzzahl, als 8 / 2 / 1 Byte gespeichert
         * smalldatetime
         *       kleiner Datums-Zeit-Datentyp (4 Byte)
         * smallmoney
         *       kleiner Währungs-Datentyp (4 Byte)
         * real
         *       Float-Datentyp mit halber Genauigkeit (4 Byte)
         * char/nchar
         *       Zeichendaten fester Länge. Die Datentypen varchar/nvarchar leisten das gewünschte
         * binary/varbinary/image
         *       Binäre Daten fester / variabler / übergroßer Länge. Für den Upload beliebiger Dateien und das anschließende Verschieben auf einen eigenen Server kann der Datentyp 'file' genutzt werden.
         * timestamp
         *       Zeitstempel der letzten Änderung. Eine solche Spalte wird jeder Tabelle intern hinzugefügt. Da eine Tabelle nur eine timestamp-Spalte enthalten kann, ist das benutzerdefinierte Erstellen einer solchen Spalte nicht möglich.
         */

        public enum DataType {
            //[Description("bit")]
            //dtBit,
            [Description("datetime")]
            dtDatetime,
            [Description("decimal")]
            dtDecimal,
            //[Description("float")]
            //dtFloat,
            [Description("int")]
            dtInt,
            [Description("money")]
            dtMoney,
            //[Description("nvarchar")]
            //dtNvarchar,
            //[Description("ntext")]
            //dtNtext,
            [Description("varchar")]
            dtvarchar,
            [Description("text")]
            dtText,
            //[Description("uniqueidentifier")]
            //dtuniqueidentifier,
            //[Description("bitset")]
            //dtBitSet,
            //[Description("file")]
            //dtFile,
            //[Description("bigint")]
            //dtBigInt,
            [Description("smallint")]
            dtSmallInt,
            [Description("tinyint")]
            dtTinyInt,
            //[Description("smalldatetime")]
            //dtSmalldatetime,
            //[Description("smallmoney")]
            //dtSmallmoney,
            //[Description("real")]
            //dtReal,
            [Description("char")]
            dtChar,
            [Description("nchar")]
            dtNchar
            //[Description("binary")]
            //dtBinary,
            //[Description("varbinary")]
            //dtVarbinary,
            //[Description("image")]
            //dtImage,
            //[Description("timestamp")]
            //dtTimestamp 
        };


        private DataType type;
        private int optionalLength; //Length or Precision inforamtion of decimal, nvarchar or varchar
        private int optionalScale;  //Scale information of decimal

        public CSQLDataType(DataType _type) :this(_type, -1)
        {
        }

        public CSQLDataType(DataType _type, int _length) : this(_type, _length, -1)
        {
        }

        public CSQLDataType(DataType _type, int _length, int _scale)
        {
            this.type = _type;
            this.optionalLength = _length;
            this.optionalScale = _scale;
        }

        public DataType getDataType()
        {
            return this.type;
        }

        public void setOptionalLength(int _otionalLength)
        {
            this.optionalLength = _otionalLength;
        }

        public void setOptionalScale(int _optionalScale)
        {
            this.optionalScale = _optionalScale;
        }

        private static IEnumerable<T> EnumToList<T>()
        {
            Type enumType = typeof(T);

            // Can't use generic type constraints on value types,
            // so have to do check like this
            if (enumType.BaseType != typeof(Enum))
                throw new ArgumentException("T must be of type System.Enum");

            Array enumValArray = Enum.GetValues(enumType);
            List<T> enumValList = new List<T>(enumValArray.Length);

            foreach (int val in enumValArray)
            {
                enumValList.Add((T)Enum.Parse(enumType, val.ToString()));
            }

            return enumValList;
        }

        public static string GetDescription(Enum value)
        {
            FieldInfo fi = value.GetType().GetField(value.ToString());
            DescriptionAttribute[] attributes = (DescriptionAttribute[])fi.GetCustomAttributes( typeof(DescriptionAttribute), false);
            return (attributes.Length > 0) ? attributes[0].Description : value.ToString();
        }

        public static CSQLDataType Parse(String _string)
        {
            CSQLDataType result = null;

            //Normalize String
            _string = _string.ToLower();
            _string = _string.Replace(" ", "");
            foreach (DataType state in EnumToList<DataType>()) //Iterate through all Enumerations
            {
                if (_string.StartsWith(GetDescription(state))) // Get SQL name of Enumeration value
                {
                    result = new CSQLDataType(state);
                    _string = _string.Substring(GetDescription(state).Length);
                    break;
                }
            }

            if (result == null)
                throw new Exception("String couldn't be parsed. Type is unknown: " + _string);

            if (_string.Length > 0)
            {
                //posible LengthInformation avalible
                if (_string.StartsWith("(") && _string.EndsWith(")"))
                {
                    _string = _string.Substring(1, _string.Length - 2);
                    int commaIndex = _string.IndexOf(',');
                    if (commaIndex == -1)
                    {
                        //only Length Information avalible
                        result.setOptionalLength(int.Parse(_string));
                    }
                    else
                    {
                        //Precision and Scale inforamtion avalible
                        result.setOptionalLength(int.Parse(_string.Substring(0, commaIndex)));
                        result.setOptionalScale(int.Parse(_string.Substring(commaIndex+1)));
                    }
                }
                else
                    throw new Exception("String couldn't be parsed. Length information don't fits the correct Syntax, Example: varchar(50) or decimal(5,3), but is: " + _string);
            }

            return result;
        }
    }
}
